#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <dirent.h>
#include <getopt.h>
#include <unistd.h>


static int strict_cnt_flag = 0;        // 严格统计标记,也就是不加上代码文件中的空行,当其为1时有效

void sy_cpy(char *dest, char *src){
    while(*src != '\0'){
        *dest = *src;
        dest++;
        src++;
    }
    *dest='\0';
}

/*判断字符串str是否以字符串end_str结尾*/
int StringEndWith(char * str, char *end_str){
    if(str == NULL || end_str == NULL) {
        return 0;
    }

    int i, j;
    int len_str     = strlen(str);
    int len_end_str = strlen(end_str);
    
    if(len_str < len_end_str){
        return 0;
    }

    for(i = len_end_str - 1, j = len_str - 1; i >= 0 && j >= 0; --i, --j){
        if(str[j] != end_str[i]){
            return 0;
        }
    }
    return 1;
}
/*判断一行字符是否全部由空格组成*/
int Legal_Line(char *cur_line_code,int len){
    if(cur_line_code == NULL){
        return 0;
    }
    for(int i = 0; i < len;++i){
        if(cur_line_code[i] != '\r' && cur_line_code[i] != ' ' && cur_line_code[i] != '\n'){
            return 1;
        }
    }
    return 0;
}

int Code_Lines_In_Dir(char *dir,int level,char *print){
    int pre_line_of_your_code;
    int line_of_your_code = 0;
    int last_file_dir_flag = 0;
    char *tmp_dir_name  = (char *)malloc(sizeof(char) * 1024);
    char *file_name     = (char *)malloc(sizeof(char) * 1024);
    /*打开文件夹*/
    DIR *open_dir = opendir(dir);
    if(open_dir == NULL){
        printf("你输入的文件夹路径:\"%s\"有误,请重新运行程序!\n",dir);
        exit(2);
    }
    

    /*读取文件夹中的文件,并且统计行数*/
    struct dirent *pre_file_entry = readdir(open_dir);
    struct dirent *cur_file_entry = NULL;
retry1:
    if(!strcmp(pre_file_entry->d_name,"..") || !strcmp(pre_file_entry->d_name,".") \
    || ( pre_file_entry->d_type != DT_DIR   && pre_file_entry->d_type != DT_REG )  \
    || ( pre_file_entry->d_type == DT_REG   && ( !StringEndWith(pre_file_entry->d_name,".c") || !StringEndWith(pre_file_entry->d_name,".h"))) ){
        pre_file_entry = readdir(open_dir);
        goto retry1;
    }
    if(pre_file_entry == EOF){
        cur_file_entry = pre_file_entry;
    }else{
        cur_file_entry = readdir(open_dir);
retry2:
        if(!strcmp(cur_file_entry->d_name,"..") || !strcmp(cur_file_entry->d_name,".")  \
        || (cur_file_entry->d_type != DT_DIR   &&  cur_file_entry->d_type != DT_REG  ) \
        || ( cur_file_entry->d_type == DT_REG && ( !StringEndWith(cur_file_entry->d_name,".c") || !StringEndWith(cur_file_entry->d_name,".h"))) ){
            cur_file_entry = readdir(open_dir);
            goto retry2;
        }
        if(cur_file_entry == EOF){
            cur_file_entry = pre_file_entry;
            last_file_dir_flag = 1;
        }
    }

do_last:
    while(cur_file_entry != EOF  && cur_file_entry != NULL){
        if(pre_file_entry->d_type == DT_DIR){ // 筛选出文件夹, DT_DIR 代表文件夹
            if(!strcmp(pre_file_entry->d_name,"..") || !strcmp(pre_file_entry->d_name,".")){
                cur_file_entry = readdir(open_dir);     // 读取下一个文件
                pre_file_entry = cur_file_entry;
                continue;
            }
            
            sy_cpy(tmp_dir_name,dir);
            int len_dir = strlen(dir);
            if(dir[len_dir-1] != '/'){
                tmp_dir_name[len_dir] = '/';
                sy_cpy(tmp_dir_name + len_dir + 1,pre_file_entry->d_name);
            }else{
                sy_cpy(tmp_dir_name + len_dir,pre_file_entry->d_name);
            }

            printf("%s",print);
            if(last_file_dir_flag){
                printf("└── %s\n",pre_file_entry->d_name);
                sy_cpy(print + level,"    \0");
            }else{
                printf("├── %s\n",pre_file_entry->d_name);
                sy_cpy(print + level,"|   \0");
            }
            print[level + 4] = '\0';
            line_of_your_code += Code_Lines_In_Dir(tmp_dir_name,level + 4,print);
        }

       if(pre_file_entry->d_type != DT_REG ){ // 筛选出常规文件, DT_REG 代表常规文件 
            cur_file_entry = readdir(open_dir);     // 读取下一个文件
            pre_file_entry = cur_file_entry;
            continue;
        }

        if(!StringEndWith(pre_file_entry->d_name,".c") && !StringEndWith(pre_file_entry->d_name,".h")){    // 筛选出.c .h文件
            cur_file_entry = readdir(open_dir);     // 读取下一个文件
            pre_file_entry = cur_file_entry;
            continue;
        }

        /*打开当前的.c或.h文件*/

        sy_cpy(file_name,dir);
        int len_dir = strlen(dir);
        if(dir[len_dir-1] != '/'){
            file_name[len_dir] = '/';
            sy_cpy(file_name + len_dir + 1,pre_file_entry->d_name);
        }else{
            sy_cpy(file_name + len_dir,pre_file_entry->d_name);
        }

        FILE *cur_file = fopen(file_name,"r");
        if(cur_file == NULL){
            printf("文件: %s 打开失败!!!\n",file_name);
            cur_file_entry = readdir(open_dir);     // 读取下一个文件
            pre_file_entry = cur_file_entry;
            continue;
        }  

        /*统计有效行数,若某一行只有空格,则不算一行代码*/
        pre_line_of_your_code = line_of_your_code;
        char cur_line_code[1024];
        char *fgets_flag = NULL;
        while((fgets_flag = fgets(cur_line_code,1024,cur_file)) != EOF && fgets_flag != NULL){
            if(!strict_cnt_flag || Legal_Line(cur_line_code,strlen(cur_line_code))){
                line_of_your_code++;
            }
        }
        fclose(cur_file);
        /*打印统计信息*/
        printf("%s",print);
        if(last_file_dir_flag){
            printf("└── %-*s <--> %d lines \n",32,pre_file_entry->d_name,line_of_your_code - pre_line_of_your_code);
        }else{
            printf("├── %-*s <--> %d lines \n",32,pre_file_entry->d_name,line_of_your_code - pre_line_of_your_code);
        }
        cur_file_entry = readdir(open_dir);     // 读取下一个文件
        pre_file_entry = cur_file_entry;
    }

    if(cur_file_entry == EOF && pre_file_entry != EOF && pre_file_entry != NULL && !last_file_dir_flag){
        cur_file_entry = pre_file_entry;
        last_file_dir_flag = 1;
        goto do_last;
    }
    free(tmp_dir_name);
    free(file_name);
    closedir(open_dir);
    return line_of_your_code;
}


int main(int argc, char *argv[])
{
    int line_of_your_code = 0;      // 你写过的代码行数       
    int free_flag = 0;

    char *code_path = NULL;         // 要统计文件所在的目录
    /*处理命令行参数*/
    const char *opt_string = "d:sh";
    int opt;
    while((opt = getopt(argc,argv,opt_string)) != EOF){
        switch (opt)
        {
        case 'd':
            code_path = optarg;     // 使用指定的文件夹
            break;
        case 's':
            strict_cnt_flag = 1;    // 启用严格统计
            break;
        case 'h':
            printf("使用选项 -d + 文件夹名 可以统计该文件夹下的所有.c .h文件中的代码行数,默认统计的是当前文件夹下的代码\n");
            printf("使用选项 -s 可以启用严格统计(也就是不统计文件中的空行)\n");
            exit(0);
            break;
        default:
            printf("%c :无效的选项!,使用-h选项运行以获得帮助\n",opt);
            exit(3);
            break;
        }
    }
    if(code_path == NULL){          // 使用默认文件夹,就是当前cntCode所在的文件夹
        free_flag = 1;
        int malloc_tst_time = 1;    // 内存申请次数
        while( (code_path = (char*)malloc(sizeof(char) * 3) ) == NULL && ++malloc_tst_time <= 10){
            printf("默认文件夹(./)创建失败！正在次尝试第%*d次创建...\n",2,malloc_tst_time);
        }
        sy_cpy(code_path,"./\0");
    }
    while(*code_path == ' ') code_path++;
    printf("%s\n",code_path);

    char *print = (char*)malloc(1024 * sizeof(char));
    sy_cpy(print,"\0");
    line_of_your_code = Code_Lines_In_Dir(code_path,0,print);
    printf("一共[%-*d]行代码\n\n\n",8,line_of_your_code);

    free(print);
    if(free_flag)
        free(code_path);
    return 0;
}